package cn.zhangfusheng.elasticsearch.config;

import cn.zhangfusheng.elasticsearch.annotation.ElasticSearchConfig;
import cn.zhangfusheng.elasticsearch.request.PaodingRequestOptions;
import cn.zhangfusheng.elasticsearch.thread.ThreadLocalDetail;
import cn.zhangfusheng.elasticsearch.transactional.TransactionalControl;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.StaticMethodMatcherPointcut;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author fusheng.zhang
 * @date 2022-04-20 12:21:10
 */
public class ElasticSearchConfigOperationSourceAdvisor extends AbstractPointcutAdvisor {

    private final static Class<ElasticSearchConfig> ELASTIC_SEARCH_CONFIG_CLASS = ElasticSearchConfig.class;
    private final RestHighLevelClient restHighLevelClient;
    private final Map<String, PaodingRequestOptions> requestOptionsGroupByName;

    public ElasticSearchConfigOperationSourceAdvisor(
            RestHighLevelClient restHighLevelClient, List<PaodingRequestOptions> paodingRequestOptions) {
        this.restHighLevelClient = restHighLevelClient;
        this.requestOptionsGroupByName = paodingRequestOptions.stream().collect(Collectors.toMap(PaodingRequestOptions::requestOptions, Function.identity()));
    }

    @Override
    public Pointcut getPointcut() {
        return new ElasticSearchConfigStaticMethodMatcherPointcut();
    }

    @Override
    public Advice getAdvice() {
        return new ElasticSearchConfigMethodInterceptor(requestOptionsGroupByName);
    }

    static class ElasticSearchConfigStaticMethodMatcherPointcut extends StaticMethodMatcherPointcut {
        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            return Objects.nonNull(method.getAnnotation(ELASTIC_SEARCH_CONFIG_CLASS));
        }
    }

    class ElasticSearchConfigMethodInterceptor implements MethodInterceptor {
        private final Map<String, PaodingRequestOptions> requestOptionsGroupByName;

        public ElasticSearchConfigMethodInterceptor(Map<String, PaodingRequestOptions> requestOptionsGroupByName) {
            this.requestOptionsGroupByName = requestOptionsGroupByName;
        }

        @Nullable
        @Override
        public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
            Throwable throwable = null;
            ElasticSearchConfig elasticSearchConfig = invocation.getMethod().getAnnotation(ElasticSearchConfig.class);
            PaodingRequestOptions paodingRequestOptions = this.requestOptionsGroupByName.get(elasticSearchConfig.requestOptions());
            ThreadLocalDetail threadLocalDetail = ThreadLocalDetail.start(elasticSearchConfig, paodingRequestOptions.options());
            try {
                return invocation.proceed();
            } catch (Throwable e) {
                throwable = e;
                throw e;
            } finally {
                Optional<TransactionalControl> transactionalControlOptional = threadLocalDetail.transactionaControl();
                if (transactionalControlOptional.isPresent()) {
                    TransactionalControl transactionalControl = transactionalControlOptional.get();
                    transactionalControl.commit(throwable, restHighLevelClient);
                }
            }
        }
    }

}
